package lispinterpreter;




/**
 * Esta clase evalua si el string ingresado cumple la sintaxis de Lisp y la
 * transforma a un objeto LispList.
 * 
 * @author Marya
 */
public class LispParser {
	
	/**
	 * Metodo toma una string y devuelve el LispList parseado totalemente si es posible.
	 * @param list
	 * @return LispList
	 * @throws ParentesisException
	 */
    public static LispList parse (String list) throws ParentesisException{
        if(!parenthesisCheck(list)){
            throw new ParentesisException("Wrong parenthesis syntax.");
        }
        LispList parsedList = LispParser.finalParse(LispParser.initialParse(list));
        LispAtom atom;
        for(int i=0; i<parsedList.size(); i++){
            if(parsedList.isAtom()){
                atom = (LispAtom) parsedList.getElement(i);
                if(atom.getType()==LispAtom.ATOM_TYPE_PARENTHESIS){
                    throw new ParentesisException("Wrong parenthesis syntax.");
                }
            }
        }
        return parsedList;
    }
    
    
    /**
     * Dice si un string tiene parentesis.
     * @param list String a evaluar.
     * @return boolean Verdadero si hay algun parentesis.
     */
    public static boolean hasParenthesis(String list){
        int contador = 0;
        for(int i=0; i<list.length(); i++){
            if(list.charAt(i)=='('|| list.charAt(i)==')'){
                contador++;
            }
        }
        return (contador!=0);
    }

    /**
     * Indica si el numero de parentesis abiertos y cerrados coinciden.
     * @param list String a evaluar.
     * @return Verdadero si coinciden el numero de parentesis abiertos y cerrados.
     */
    public static boolean parenthesisNumberMatches(String list){
        int abiertos=0;
        int cerrados=0;
        for(int i=0; i<list.length(); i++){
            if(list.charAt(i)=='('){
                abiertos++;
            }
            if(list.charAt(i)==')'){
                cerrados++;
            }
        }
        return (abiertos==cerrados);
    }

    /**
     * Toma un string y remueve un substring contenido en una sublista.
     * @param list
     * @return String list sin una sublista
     * @throws ParentesisException
     */
    public static String parenthesisOneToOne(String list) throws ParentesisException{
        int inicio=-1, fin = -1;
        for(int i=0; i<list.length(); i++){
            if(list.charAt(i)=='('){
                inicio = i;
            }
            if(list.charAt(i)==')'){
                fin = i;
                if(inicio == -1){
                    throw new ParentesisException("Wrong parenthesis syntax.");
                }
                break;
            }
        }
        return list.substring(0, inicio)+ list.substring(fin+1);
    }

    /**
     * Devuelve verdadero si la String lista es parseable.
     * @param list
     * @return Verdadero si la lista es parseable.
     */
    public static boolean parenthesisCheck(String list){
        while(parenthesisNumberMatches(list)&&hasParenthesis(list)){
            try {
                list = parenthesisOneToOne(list);
            } catch (ParentesisException ex) {
//                Logger.getLogger(LispParser.class.getName()).log(Level.SEVERE, null, ex);
                System.out.println("Wrong parenthesis syntax.");
            }
        }
        if(!parenthesisNumberMatches(list)){
            System.out.println("Wrong parenthesis syntax.");
            return false;

        }
        return true;
    }

    /**
     * Metodo que toma el initial parse e itera firsComposedListParse
     * hasta terminar el parseo.
     * @param partiallParsedList Lista semi-parseada.
     * @return LispList Lista completamente parseada.
     */
    public static LispList finalParse(LispList partiallParsedList) {

	boolean control = false;

	while (control == false) {
	    partiallParsedList = firstComposedListParse(partiallParsedList);
	    if (partiallParsedList.isParsed()) {
		control = true;
	    }
	}

	return partiallParsedList;
    }

    /**
     * Metodo toma una lista parcialmente parseada y parsea una sublista.
     * @param partiallParsedList
     * @return Lista con una sublista mas parseada.
     */
    public static LispList firstComposedListParse(LispList partiallParsedList) {
	LispList reemplazo = new LispList();
	int inicio = 0;
	int fin = 0;
	for (int i = 0; i < partiallParsedList.size(); i++) {
	    LispList sublista = (LispList) partiallParsedList.getElement(i);
	    if (sublista.sAtomVerifier()) {
		LispAtom atomo = (LispAtom) sublista;
		if (atomo.catom == ')') {
		    for (int j = i; j >= 0; j--) {
			LispList sublista2 = partiallParsedList.getElement(j);
			if (sublista2.sAtomVerifier()) {
			    LispAtom atomo2 = (LispAtom) sublista2;
			    if (atomo2.catom == '(') {
				reemplazo = extraerLista(partiallParsedList,
					j + 1, i - 1);
				inicio = j;
				fin = i;
				int limite = fin - inicio;
				int k = 1;
				int elimino = inicio + 1;
				while (k <= limite) {
				    partiallParsedList.linkedList
					    .remove(elimino);
				    k++;
				}
				partiallParsedList.replaceElement(inicio,
					reemplazo);
				return partiallParsedList;
			    }
			}
		    }
		}
	    }
	}
	return partiallParsedList;

    }

    /**
     * Extrae una sublista LispList de una lista LispList.
     * @param lista
     * @param inicio
     * @param fin
     * @return LispList sublista dentro de los parametros indicados.
     */
    public static LispList extraerLista(LispList lista, int inicio, int fin) {
	LispList nuevaLista = new LispList();
	for (int i = inicio; i <= fin; i++) {
	    nuevaLista.addElement(lista.getElement(i));
	}
	return nuevaLista;
    }

    /**
     * Parseo inicial, donde se transforman los parentesis a elementos tipo cuatro,
     * y no se distingue completamente la estructura de la lista. 
     * @param chain
     * @return LispList inicial para parseo.
     */
    public static LispList initialParse(String chain) {
	LispList semiParsedList = new LispList();
	String atomString = "";
	chain = removeFirstLastSpaces(removeFirstLastParenthesis(removeExtraSpaces(chain)));
	int atomLength = 0;
	for (int i = 0; i < chain.length(); i++) {
	    if (chain.charAt(i) == '(') {
		if (chain.charAt(i - 1) != ' ') {
		    if (chain.charAt(i - 1) == ')') {
			atomString = chain.substring(i, i + 1);
			semiParsedList.addElement(atomInstance(atomString));
		    } else {
			atomString = chain.substring(i - atomLength, i);
			semiParsedList.addElement(atomInstance(atomString));
			atomString = chain.substring(i, i + 1);
			semiParsedList.addElement(atomInstance(atomString));
		    }
		}

		else {
		    atomString = chain.substring(i, i + 1);
		    semiParsedList.addElement(atomInstance(atomString));
		}

		atomLength = 0;
	    } else if (chain.charAt(i) == ')') {
		if (chain.charAt(i - 1) != ' ') {
		    if (chain.charAt(i - 1) == ')') {
			atomString = chain.substring(i, i + 1);
			semiParsedList.addElement(atomInstance(atomString));
		    } else {
			atomString = chain.substring(i - atomLength, i);
			semiParsedList.addElement(atomInstance(atomString));
			atomString = chain.substring(i, i + 1);
			semiParsedList.addElement(atomInstance(atomString));
		    }
		} else {
		    atomString = chain.substring(i, i + 1);
		    semiParsedList.addElement(atomInstance(atomString));
		}
		atomLength = 0;
	    } else if (chain.charAt(i) == ' ') {
		if (chain.charAt(i - 1) == ')' || chain.charAt(i - 1) == '(') {

		} else {
		    atomString = chain.substring(i - atomLength, i);
		    semiParsedList.addElement(atomInstance(atomString));
		    atomLength = 0;
		}
	    } else if (i == chain.length() - 1) {
		atomString = chain.substring(i - atomLength);
		// atomString = String.valueOf(chain.charAt(i));

		semiParsedList.addElement(atomInstance(atomString));

	    }

	    else {
		atomLength++;
	    }
	}
	return semiParsedList;
    }

    /**
     * @pre String a parsear es una lista simple de Lisp (no tiene sublistas) y
     *      esta bien definida.
     * @param chain
     * @return LispList
     */
    public static LispList parseSimple(String chain) {
	LispList simpleList = new LispList();
	String atom = "";
	chain = removeFirstLastSpaces(removeFirstLastParenthesis(removeExtraSpaces(chain)));
	int lastSpace = chain.lastIndexOf(' ');
	int lastAtomEnded = 0;
	if (chain.length() == 2) {
	    return new LispAtom(false);
	}
	for (int i = 0; i < chain.length(); i++) {
	    if (i == lastSpace) {
		atom = chain.substring(i);
		simpleList.addElement(atomInstance(atom));
	    }
	    if (chain.charAt(i) == ' ') {
		atom = chain.substring(lastAtomEnded, i);
		lastAtomEnded = i + 1;
		simpleList.addElement(atomInstance(atom));
	    }
	}

	return simpleList;
    }

    /**
     * Indica si la lista chain es una sublista simple.
     * @param chain
     * @return
     */
    public boolean formulaIsSimple(String chain) {
	return rightPlacedParenthesisSimple(chain)
		&& twoParenthesisSimple(chain);
    }

    public boolean rightPlacedParenthesisSimple(String chain) {
	if (chain.charAt(0) == '(' && chain.charAt(chain.length() - 1) == ')') {
	    return true;
	} else {
	    return false;
	}
    }

    public boolean twoParenthesisSimple(String chain) {
	int count = 0;
	for (int i = 0; i < chain.length(); i++) {
	    if (chain.charAt(i) == '(') {
		count++;
	    }
	    if (chain.charAt(i) == ')') {
		count++;
	    }
	}
	return (count == 2);

    }

    /**
     * Pone un espacio para cualquier numero de espacios consecutivos en un String.
     * @param chain
     * @return String sin espacios consecutivos.
     */
    public static String removeExtraSpaces(String chain) {
	int nextConsecutiveSpacesNumber = 0;
	boolean lastCharWasEspace = true;

	for (int i = 0; i < chain.length(); i++) {
	    if (chain.charAt(i) == ' ') {
		nextConsecutiveSpacesNumber++;
		lastCharWasEspace = true;

	    } else {
		if (lastCharWasEspace) {
		    if (nextConsecutiveSpacesNumber > 1) {
			chain = chain.substring(0, i + 1
				- nextConsecutiveSpacesNumber)
				+ chain.substring(i);
			i = 0;
		    }
		}
		lastCharWasEspace = false;
		nextConsecutiveSpacesNumber = 0;

	    }
	}

	return chain;
    }

    /**
     * Quita el primero y ultimo parentesis de un String, si existen.
     * @param chain
     * @return String sin parentesis inicial y final.
     */
    public static String removeFirstLastParenthesis(String chain) {
	if (chain.charAt(0) == '(') {
	    chain = chain.substring(1);
	}
	if (chain.charAt(chain.length() - 1) == ')') {
	    chain = chain.substring(0, chain.length() - 1);
	}
	return chain;
    }
    
    /**
     * Quita el primer y ultimo espacio de un String, si son espacios.
     * @param chain
     * @return String sin primero y ultimo parentesis.
     */
    public static String removeFirstLastSpaces(String chain) {
	if (chain.charAt(0) == ' ') {
	    chain = chain.substring(1);
	}
	if (chain.charAt(chain.length() - 1) == ' ') {
	    chain = chain.substring(0, chain.length() - 1);
	}
	return chain;
    }

    /**
     * Metodo estatico que puede instanciar un LispAtom a partir de un String.
     * @param atom
     * @return LispAtom
     */
    public static LispList atomInstance(String atom) {
	@SuppressWarnings("unused")
	LispAtom newAtom;
	if (LispAtom.typeIdentifier(atom) == LispAtom.ATOM_TYPE_FLOAT) {
	    return newAtom = new LispAtom(Float.parseFloat(atom));
	}
	if (LispAtom.typeIdentifier(atom) == LispAtom.ATOM_TYPE_BOOLEAN) {
	    if (atom.charAt(0) == 'T') {
		return newAtom = new LispAtom(true);
	    } else {
		return newAtom = new LispAtom(false);
	    }
	}
	if (LispAtom.typeIdentifier(atom) == LispAtom.ATOM_TYPE_STRING) {

	    return newAtom = (new LispAtom(atom));
	}
	if (LispAtom.typeIdentifier(atom) == LispAtom.ATOM_TYPE_PARENTHESIS) {
	    return newAtom = (new LispAtom(atom));
	}
	return newAtom = new LispAtom(false);
    }
}
